package com.tong.lettuce.lock.interceptor;

import com.tong.lettuce.lock.annotation.RedisLock;
import com.tong.lettuce.lock.core.RedisLockable;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AopInvocationException;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.UUID;

@Slf4j
public class RedisLockMethodInterceptor implements MethodInterceptor {
    private final Logger log = LoggerFactory.getLogger(RedisLockMethodInterceptor.class);

    private RedisLockExpressionEvaluator expressionEvaluator = new RedisLockExpressionEvaluator();

    private RedisLockable redisLockable;

    public RedisLockMethodInterceptor(RedisLockable redisLockable) {
        this.redisLockable = redisLockable;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        invocation.getMethod();
        RedisLock redisLock = invocation.getMethod().getAnnotation(RedisLock.class);
        Assert.notNull(redisLock, "@RedisLock not found");
        String lockName = redisLock.lockName();
        Assert.hasLength(lockName, "lockName must not be empty");
        String paramKey = null;
        if (StringUtils.hasLength(redisLock.lockKey())) {
            paramKey = getLockParamKey(redisLock.lockKey(), invocation);
        }
        String redisKey = paramKey == null ? lockName : lockName + ":" + paramKey;
        int expireSeconds = redisLock.expireSeconds();
        String uuid = UUID.randomUUID().toString();
        if (redisLockable.lock(redisKey, uuid, expireSeconds, redisLock.retryCount(), redisLock.retryInterval())) {
            try {
                log.debug("[redis-lock fetch success, start business...");
                return invocation.proceed();
            }
            catch (Throwable throwable) {
                log.error("redis-lock business exception", throwable);
                throw throwable;
            }
            finally {
                boolean unlockResult = redisLockable.unlock(lockName, uuid);
                log.debug("redis-lock release : {}", unlockResult);
            }
        }
        else {
            throw new AopInvocationException("redis-lock fetch fail");
        }
    }

    /**
     * 获取锁的key值
     * @param lockKey
     * @param invocation
     * @return
     */
    private String getLockParamKey(String lockKey, MethodInvocation invocation) {
        EvaluationContext evaluationContext = expressionEvaluator.createEvaluationContext(invocation.getThis(),
                invocation.getMethod(), invocation.getArguments());
        AnnotatedElementKey elementKey = new AnnotatedElementKey(invocation.getMethod(),
                invocation.getThis().getClass());
        Object key = expressionEvaluator.key(lockKey, elementKey, evaluationContext);
        if (key == null) {
            return null;
        }
        return key.toString();
    }
}
